package com.googlecode.groovy_toy_orm

import com.googlecode.groovy_toy_orm.framework.BaseFunctionalTest
import com.googlecode.groovy_toy_orm.internal.Retrier
import com.googlecode.groovy_toy_orm.internal.TestLoggerAppender
import com.googlecode.groovy_toy_orm.internal.Retrier
import com.googlecode.groovy_toy_orm.internal.TestLoggerAppender
import groovy.transform.ToString
import org.apache.log4j.Logger
import org.apache.log4j.spi.LoggingEvent
import org.testng.annotations.BeforeClass
import org.testng.annotations.DataProvider
import org.testng.annotations.Test

//import TestAppender
import java.util.concurrent.atomic.AtomicInteger

/**
 * Created with IntelliJ IDEA.
 * User: MalyutinAA
 * Date: 12.04.13
 * Time: 15:10
 */
class RetrierTest extends BaseFunctionalTest {
    /**
     * Именует параметры для объектов класса Retrier.
     */
    enum RetrierMethod {
        LOGGING({ Retrier r, Object param -> r.withLogging(param as String) }),
        ATTEMPTS({ Retrier r, Object param -> r.withAttempts(param as Integer) }),
        TIMEOUT({ Retrier r, Object param -> r.withTimeout(param as Integer) })

        Closure<Retrier> callClosure

        RetrierMethod(Closure<Retrier> callClosure) {
            this.callClosure = callClosure
        }

        Retrier applyMethod(Retrier retrier, Object param) {
            callClosure(retrier, param)
            retrier
        }
    }

    /**
     * Вызывает объект класса Retrier с указанным именованным параметром.
     */

    @ToString()
    class RetrierMethodCall {
        RetrierMethod method
        Object param

        Retrier applyMethod(Retrier retrier) {
            method.applyMethod(retrier, param)
            retrier
        }
    }

    /**
     * Closure для передачи в метод exec.<BR/>
     * Увеличивает счетчик на единицу через заданный интервал времени,
     * проверяя этот счетчик на непревышение установленного количества попыток.
     */
    static Closure<Boolean> retrierCallbackClosure(int numAttempts, int intervalMsec) {

        return { AtomicInteger counter ->
            sleep(intervalMsec)
            counter.set(counter.get() + 1)
            counter.get() >= numAttempts
        }
    }

    /*
     * Преобразовывает в String последнее сообщение из списка залогированных событий.
     */
    private static String getLastMessageText(List<LoggingEvent> loggedMessages) {
        loggedMessages.last().getMessage().toString()
    }

    /**
     * Параметры для теста класса Retrier:<BR/>
     * [Описание,<BR/>
     * параметры retrierCallbackClousure для exec,<BR/>
     * вызываемые методы класса Retrier с указанными параметрами,<BR/>
     * ожидаемое значение счетчика,<BR/>
     * ожидаемое возвращаемое значение exec,<BR/>
     * ожидаемая часть сообщения логгера(при наличии)].
     */
    @DataProvider(parallel = false)
    def Object[][] testParameters() {
        Object[][] data = [
                ["Test without conditions", retrierCallbackClosure(5, 0), [], 5, true, ""]
                , ["Attempts num not exceeded", retrierCallbackClosure(5, 0), [new RetrierMethodCall(method: RetrierMethod.ATTEMPTS, param: 5)], 5, true, ""]
                , ["Attempts num exceeded without timeout", retrierCallbackClosure(7, 0), [new RetrierMethodCall(method: RetrierMethod.ATTEMPTS, param: 5)], 6, false, ""]
                , ["Attempts num exceeded with true timeout", retrierCallbackClosure(7, 100), [new RetrierMethodCall(method: RetrierMethod.ATTEMPTS, param: 5), new RetrierMethodCall(method: RetrierMethod.TIMEOUT, param: 2000)], 6, false, ""]
                , ["Timeout is not exceeded", retrierCallbackClosure(4, 100), [new RetrierMethodCall(method: RetrierMethod.TIMEOUT, param: 500)], null, true, ""]
                , ["Timeout is exceeded without number of attempts", retrierCallbackClosure(4, 100), [new RetrierMethodCall(method: RetrierMethod.TIMEOUT, param: 300)], null, false, ""]
                , ["Timeout is exceeded with true number of attempts", retrierCallbackClosure(4, 100), [new RetrierMethodCall(method: RetrierMethod.ATTEMPTS, param: 5), new RetrierMethodCall(method: RetrierMethod.TIMEOUT, param: 350)], 3, false, ""]
                , ["Timeout is exceeded with logging", retrierCallbackClosure(4, 100), [new RetrierMethodCall(method: RetrierMethod.TIMEOUT, param: 300), new RetrierMethodCall(method: RetrierMethod.LOGGING, param: "timeout")], null, false, "failed: Timeout"]
                , ["Timeout is not exceeded with logging", retrierCallbackClosure(4, 100), [new RetrierMethodCall(method: RetrierMethod.TIMEOUT, param: 500), new RetrierMethodCall(method: RetrierMethod.LOGGING, param: "timeout")], null, true, ""]
                , ["Attempts num exceeded with logging", retrierCallbackClosure(7, 0), [new RetrierMethodCall(method: RetrierMethod.ATTEMPTS, param: 5), new RetrierMethodCall(method: RetrierMethod.LOGGING, param: "attempts")], 6, false, "failed:number of attempts"]
                , ["Attempts num not exceeded with logging", retrierCallbackClosure(5, 0), [new RetrierMethodCall(method: RetrierMethod.ATTEMPTS, param: 5), new RetrierMethodCall(method: RetrierMethod.LOGGING, param: "attempts")], 5, true, ""]
        ]
        data
    }

    @BeforeClass
    public void initAppender() {
        // Adding test appender to Retrier class logger, so that we could monitor messages, logged by Retrier

        Logger logger = Logger.getLogger(Retrier.class)
        logger.removeAllAppenders()
        logger.addAppender(new TestLoggerAppender())
    }

    /**
     * Параметризованный тест для класса Retrier.<BR/>
     * Параметры:
     * {@link RetrierTest#testParameters()}
     */
    @Test(dataProvider = 'testParameters')
    public void parametrizedTest(String description, Closure<Boolean> callbackClosureParam, List<RetrierMethodCall> calls, Integer expectedCount, boolean expectedResult, String anticipatedLog) {
        asTest {

            TestLoggerAppender.clear()

            AtomicInteger countRef = new AtomicInteger(0)

            //Передаем в closure счетчик вызовов
            def callbackClosure = callbackClosureParam.curry(countRef)

            Retrier thisRetrier = new Retrier()

            //Инициализируем Retrier в соответствии с параметрами теста: вызываем его методы установки параметров в нужном порядке
            calls.each { RetrierMethodCall thisCall ->
                thisRetrier = thisCall.applyMethod(thisRetrier)
            }

            boolean res = thisRetrier.exec(callbackClosure)

            def loggedMessages = TestLoggerAppender.getList();

            /* ===================
             * Проверки
              ================= */
            boolean isLogged = !loggedMessages.isEmpty()
            if (anticipatedLog != "") {
                assert isLogged
                String messageText = getLastMessageText(loggedMessages)
                assert messageText.indexOf(anticipatedLog) != -1, "Последнее залогированное сообщение ('${messageText}') не содержит ожидаемого текста ('${anticipatedLog}')" //
            }

            if (expectedCount.asBoolean())
                assert countRef.get() == expectedCount, "Число вызовов callbackClosure не соответствует ожидаемому"

            assert res == expectedResult, "Результат, возвращенный методом exec не соответствует ожидаемому"
        }
    }
}

